﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;

namespace Cdhpy.Util
{
    public enum CipherFormat
    {
        Base64 = 0,
        Hex = 1
    }

    /// <summary>
    /// Encryption algorithms 
    /// </summary>
    public sealed class Encrypt
    {
        /// <summary>
        /// Compute MD5 Hash(32-bits)
        /// </summary>
        /// <param name="strText">string to be hashed</param>
        /// <returns></returns>
        public static string Md5(string strText)
        {
            MD5 md5Hasher = MD5.Create();
            byte[] data = md5Hasher.ComputeHash(Encoding.Default.GetBytes(strText));
            StringBuilder sBuilder = new StringBuilder();
            for (int i = 0; i < data.Length; i++)
            {
                sBuilder.Append(data[i].ToString("x2"));
            }
            return sBuilder.ToString();
        }

        /// <summary>
        /// Check if the string matches md5 format
        /// </summary>
        /// <param name="md5"></param>
        /// <returns></returns>
        public static bool IsValidMD5(string md5)
        {
            if (md5 == null || md5.Length != 32) return false;
            return md5.All(x => (x >= '0' && x <= '9') || (x >= 'a' && x <= 'f') || (x >= 'A' && x <= 'F'));
        }

        /// <summary>
        /// Compute SHA1 Hash
        /// </summary>
        /// <param name="strText">string to be hashed</param>
        /// <exception cref="ArgumentNullException"></exception>
        /// <returns></returns>
        public static string Sha1(string strText)
        {
            string sha1Str = "";
            using (SHA1Managed sha1 = new SHA1Managed())
            {
                var hash = sha1.ComputeHash(Encoding.UTF8.GetBytes(strText));
                var sb = new StringBuilder(hash.Length * 2);

                foreach (byte b in hash)
                {
                    sb.Append(b.ToString("X2"));
                }
                sha1Str = sb.ToString();
            }
            return sha1Str;
        }

        /// <summary>
        /// Compute SHA256 Hash
        /// </summary>
        /// <param name="strText">string to be hashed</param>
        /// <exception cref="ArgumentNullException"></exception>
        /// <returns></returns>
        public static string Sha256(string strText)
        {
            using (var hash = SHA256.Create())
            {
                return string.Join("", hash
                  .ComputeHash(Encoding.UTF8.GetBytes(strText))
                  .Select(item => item.ToString("x2")));
            }
        }

        /// <summary>
        /// Compute MD5 Hash of a file(32-bits)
        /// </summary>
        /// <param name="filePath">path of the file</param>
        /// <returns></returns>
        public static string FileMd5(string filePath)
        {
            string md5Val = "";
            try
            {
                using (var md5 = MD5.Create())
                {
                    using (var stream = System.IO.File.OpenRead(filePath))
                    {
                        md5Val = BitConverter.ToString(md5.ComputeHash(stream)).Replace("-", "").ToLower();
                    }
                    return md5Val;
                }
            }
            catch
            {
                throw;
            }
        }

        /// <summary>
        /// Base64编码（UTF-8格式）
        /// </summary>
        /// <param name="source">待编码的明文</param>
        /// <returns></returns>
        public static string Base64Encode(string source)
        {
            try
            {
                byte[] encData_byte = new byte[source.Length];
                encData_byte = System.Text.Encoding.UTF8.GetBytes(source);
                string encodedData = Convert.ToBase64String(encData_byte);
                return encodedData;
            }
            catch (Exception e)
            {
                throw new Exception("Error in base64Encode" + e.Message);
            }
        }

        /// <summary>
        /// Base64解码（UTF-8格式）
        /// </summary>
        /// <param name="source">待解码的密文</param>
        /// <returns></returns>
        public static string Base64Decode(string source)
        {
            if (String.IsNullOrEmpty(source))
                return "";

            try
            {
                System.Text.UTF8Encoding encoder = new System.Text.UTF8Encoding();
                System.Text.Decoder utf8Decode = encoder.GetDecoder();
                byte[] todecode_byte = Convert.FromBase64String(source);
                int charCount = utf8Decode.GetCharCount(todecode_byte, 0, todecode_byte.Length);
                char[] decoded_char = new char[charCount];
                utf8Decode.GetChars(todecode_byte, 0, todecode_byte.Length, decoded_char, 0);
                string result = new String(decoded_char);
                return result;
            }
            catch (Exception e)
            {
                throw new Exception("Error in base64Decode" + e.Message);
            }
        }

        /// <summary>
        /// Create a RSA key pair
        /// </summary>
        /// <returns>key: public key,    value: private key</returns>
        public static KeyValuePair<string, string> CreateRSAKeyPair()
        {
            RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();
            var privateKey = rsa.ToXmlString(true);    
            var publicKey = rsa.ToXmlString(false);
            var kvp = new KeyValuePair<string, string>(publicKey, privateKey);
            return kvp;
        }

        /// <summary>
        /// RSA加密
        /// </summary>
        /// <param name="code">明文</param>
        /// <returns>密文</returns>
        public static string RSAEncrypt(string code, string publicKey)
        {
            if (string.IsNullOrEmpty(publicKey))
            {
                throw new Exception("publicKey can not be empty!");
            }
            string key = publicKey;
            byte[] source = Encoding.Default.GetBytes(code);
            byte[] ciphertext;

            RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();
            rsa.FromXmlString(key);
            ciphertext = rsa.Encrypt(source, false);
            StringBuilder sb = new StringBuilder();
            foreach (byte b in ciphertext)
            {
                sb.AppendFormat("{0:X2}", b);
            }
            return sb.ToString();
        }

        /// <summary>
        /// RSA解密
        /// </summary>
        /// <param name="code">密文</param>
        /// <returns>明文</returns>
        public static string RSADecrypt(string code, string privateKey)
        {
            if (string.IsNullOrEmpty(privateKey))
            {
                throw new Exception("privateKey can not be empty!");
            }
            string key = privateKey;
            byte[] ciphertext = new byte[code.Length / 2];
            for (int x = 0; x < code.Length / 2; x++)
            {
                int i = (Convert.ToInt32(code.Substring(x * 2, 2), 16));
                ciphertext[x] = (byte)i;
            }
            byte[] source;

            RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();
            rsa.FromXmlString(key);
            source = rsa.Decrypt(ciphertext, false);

            return Encoding.Default.GetString(source);
        }

        /// <summary>
        /// An easy way to Encrypt text using AES algorithm. 
        /// </summary>
        /// <param name="plainText">The text that you want to encrypt</param>
        /// <param name="key">Symmetric key that is used for encryption and decryption.</param>
        /// <returns>hexed encrypted string</returns>
        public static string AESEncrypt(string plainText, string key)
        {
            return AESEncrypt(plainText, key, "1234567890123456", CipherFormat.Hex);
        }

        /// <summary>
        /// An easy way to Decrypt encripted hex string using AES algorithm.
        /// </summary>
        /// <param name="cipherText">Your encrypted string</param>
        /// <param name="key">Symmetric key that is used for encryption and decryption.</param>
        /// <returns></returns>
        public static string AESDecrypt(string cipherText, string key)
        {
            return AESDecrypt(cipherText, CipherFormat.Hex,  key, "1234567890123456");
        }

        /// <summary>
        /// Encrypt text using AES algorithm. 
        /// </summary>
        /// <param name="plainText">The text that you want to encrypt</param>
        /// <param name="key">Symmetric key that is used for encryption and decryption.</param>
        /// <param name="iv">Initialization vector (IV) for the symmetric algorithm.</param>
        /// <param name="cipherFormat">The format of returned cipher string</param>
        /// <returns></returns>
        public static string AESEncrypt(string plainText, string Key, string IV, CipherFormat cipherFormat)
        {
            byte[] key = Encoding.UTF8.GetBytes(Key);
            byte[] iv = Encoding.UTF8.GetBytes(IV);
            byte[] cipherBytes = AESEncryptBase(plainText, key, iv);
            if(cipherFormat == CipherFormat.Hex)
            {
                StringBuilder sb = new StringBuilder();
                foreach (byte b in cipherBytes)
                {
                    sb.AppendFormat("{0:X2}", b);
                }
                return sb.ToString();
            }
            else
            {
                return Convert.ToBase64String(cipherBytes);
            }
        }

        /// <summary>
        /// Decrypt text using AES algorithm.
        /// </summary>
        /// <param name="cipherText">Your encrypted string</param>
        /// <param name="cipherFormat">The encode format of cipher string</param>
        /// <param name="key">Symmetric key that is used for encryption and decryption.</param>
        /// <param name="iv">Initialization vector (IV) for the symmetric algorithm.</param>
        /// <returns>Decrypted string</returns>
        public static string AESDecrypt(string cipherString, CipherFormat cipherFormat,  string Key, string IV)
        {
            // Check arguments.
            if (cipherString == null || cipherString.Length <= 0)
                throw new ArgumentNullException("cipherText");
            if (Key == null || Key.Length <= 0)
                throw new ArgumentNullException("Key");
            if (IV == null || IV.Length <= 0)
                throw new ArgumentNullException("IV");

            byte[] cipherText;
            if(cipherFormat== CipherFormat.Hex)
            {
                cipherText = System.Linq.Enumerable.Range(0, cipherString.Length / 2).Select(x => Convert.ToByte(cipherString.Substring(x * 2, 2), 16)).ToArray(); //hex to byte[]
            }
            else
            {
                cipherText = Convert.FromBase64String(cipherString);
            }

            // Declare the string used to hold
            // the decrypted text.
            string plaintext = null;

            // Create an Aes object
            // with the specified key and IV.
            using (Aes aesAlg = Aes.Create())
            {
                aesAlg.Key = Encoding.UTF8.GetBytes(Key);
                aesAlg.IV = Encoding.UTF8.GetBytes(IV);

                // Create a decrytor to perform the stream transform.
                ICryptoTransform decryptor = aesAlg.CreateDecryptor(aesAlg.Key, aesAlg.IV);

                // Create the streams used for decryption.
                using (MemoryStream msDecrypt = new MemoryStream(cipherText))
                {
                    using (CryptoStream csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read))
                    {
                        using (StreamReader srDecrypt = new StreamReader(csDecrypt))
                        {

                            // Read the decrypted bytes from the decrypting stream
                            // and place them in a string.
                            plaintext = srDecrypt.ReadToEnd();
                        }
                    }
                }

            }

            return plaintext;
        }

        /// <summary>
        /// Encrypt text using AES algorithm.  <see cref="https://msdn.microsoft.com/en-us/library/system.security.cryptography.aes(v=vs.110).aspx"/>
        /// </summary>
        /// <param name="plainText">The text that you want to encrypt</param>
        /// <param name="key">Symmetric key that is used for encryption and decryption.</param>
        /// <param name="iv">Initialization vector (IV) for the symmetric algorithm.</param>
        /// <returns></returns>
        public static byte[] AESEncryptBase(string plainText, byte[] Key, byte[] IV)
        {
            // Check arguments.
            if (plainText == null || plainText.Length <= 0)
                throw new ArgumentNullException("plainText");
            if (Key == null || Key.Length <= 0)
                throw new ArgumentNullException("Key");
            if (IV == null || IV.Length <= 0)
                throw new ArgumentNullException("IV");
            byte[] encrypted;

            // Create an Aes object
            // with the specified key and IV.
            using (Aes aesAlg = Aes.Create())
            {
                aesAlg.Key = Key;
                aesAlg.IV = IV;

                // Create a decrytor to perform the stream transform.
                ICryptoTransform encryptor = aesAlg.CreateEncryptor(aesAlg.Key, aesAlg.IV);

                // Create the streams used for encryption.
                using (MemoryStream msEncrypt = new MemoryStream())
                {
                    using (CryptoStream csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write))
                    {
                        using (StreamWriter swEncrypt = new StreamWriter(csEncrypt))
                        {
                            //Write all data to the stream.
                            swEncrypt.Write(plainText);
                        }
                        encrypted = msEncrypt.ToArray();
                    }
                }
            }

            // Return the encrypted bytes from the memory stream.
            return encrypted;
        }

        /// <summary>
        /// Decrypt text using AES algorithm.
        /// </summary>
        /// <param name="cipherText">Your encrypted string</param>
        /// <param name="key">Symmetric key that is used for encryption and decryption.</param>
        /// <param name="iv">Initialization vector (IV) for the symmetric algorithm.</param>
        /// <returns>Decrypted string</returns>
        public static string AESDecryptBase(byte[] cipherText, byte[] Key, byte[] IV)
        {
            // Check arguments.
            if (cipherText == null || cipherText.Length <= 0)
                throw new ArgumentNullException("cipherText");
            if (Key == null || Key.Length <= 0)
                throw new ArgumentNullException("Key");
            if (IV == null || IV.Length <= 0)
                throw new ArgumentNullException("IV");

            // Declare the string used to hold
            // the decrypted text.
            string plaintext = null;

            // Create an Aes object
            // with the specified key and IV.
            using (Aes aesAlg = Aes.Create())
            {
                aesAlg.Key = Key;
                aesAlg.IV = IV;

                // Create a decrytor to perform the stream transform.
                ICryptoTransform decryptor = aesAlg.CreateDecryptor(aesAlg.Key, aesAlg.IV);

                // Create the streams used for decryption.
                using (MemoryStream msDecrypt = new MemoryStream(cipherText))
                {
                    using (CryptoStream csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read))
                    {
                        using (StreamReader srDecrypt = new StreamReader(csDecrypt))
                        {

                            // Read the decrypted bytes from the decrypting stream
                            // and place them in a string.
                            plaintext = srDecrypt.ReadToEnd();
                        }
                    }
                }

            }

            return plaintext;
        }

        /// <summary>
        /// Encrypt text using DES algorithm.
        /// </summary>
        /// <param name="text"></param>
        /// <param name="key">Symmetric key that is used for encryption and decryption.</param>
        /// <param name="iv">Initialization vector (IV) for the symmetric algorithm.</param>
        /// <returns></returns>
        public static string DESEncrypt(string text, string key, string iv)
        {
            var pText = Encoding.UTF8.GetBytes(text);
            using (var desCryptoService = new DESCryptoServiceProvider())
            {
                desCryptoService.Key = Encoding.ASCII.GetBytes(key);
                desCryptoService.IV = Encoding.ASCII.GetBytes(iv);
                using (var memoryStream = new MemoryStream())
                {
                    var cryptoStream = new CryptoStream(memoryStream, desCryptoService.CreateEncryptor(), CryptoStreamMode.Write);
                    cryptoStream.Write(pText, 0, pText.Length);
                    cryptoStream.Close();
                    memoryStream.Close();
                    var result = Encoding.Default.GetString(memoryStream.ToArray());
                    return result;
                }
            }
        }

        /// <summary>
        /// Decrypt text using DES algorithm.
        /// </summary>
        /// <param name="encryptedText"></param>
        /// <param name="key">Symmetric key that is used for encryption and decryption.</param>
        /// <param name="iv">Initialization vector (IV) for the symmetric algorithm.</param>
        /// <returns></returns>
        public static string DESDecrypt(string encryptedText, string key, string iv)
        {
            var encryptedTextByte = Encoding.Default.GetBytes(encryptedText); // parse text to bites array
            using (var desCryptoService = new DESCryptoServiceProvider())
            {
                desCryptoService.Key = Encoding.ASCII.GetBytes(key);
                desCryptoService.IV = Encoding.ASCII.GetBytes(iv);
                var decryptor = desCryptoService.CreateDecryptor(desCryptoService.Key, desCryptoService.IV);
                using (var msDecrypt = new MemoryStream(encryptedTextByte))
                {
                    using (var csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read))
                    {
                        using (var srDecrypt = new StreamReader(csDecrypt))
                        {
                            var res = srDecrypt.ReadToEnd();
                            return res;
                        }
                    }
                }
            }
        }
    }
}
